package com.qyer.commons.utils;

import static org.apache.commons.collections4.CollectionUtils.subtract;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
 * User: Z J Wu Date: 2016/02/18 Time: 15:16 Package: com.qyer.commons.utils
 */
public class CommonUtils {

  private static final Logger LOGGER = LoggerFactory.getLogger(CommonUtils.class);

  private static final Random RANDOM = new Random();

  public static final int LEFT = 0;
  public static final int RIGHT = 1;

  private static final int LENGTH_OF_BYTE = 1 << 3;

  private final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
                                        'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                        'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
  };

  public static void waitForSignal(CountDownLatch signal, String name) {
    try {
      signal.await();
    } catch (InterruptedException e) {
      LOGGER.error("Can not wait any longer(" + name + ").", e);
    }
  }

  public static void printFileContentInClassPath(String file) {
    try (InputStream is = CommonUtils.class.getClassLoader().getResourceAsStream(file);
         BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
      String line;
      while ((line = br.readLine()) != null) {
        System.out.println(line);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public static <T> List<T> splitList(List<T> origin, int leftOrRight) {
    int size = origin.size(), middle = size / 2;
    switch (leftOrRight) {
      case LEFT:
        return new ArrayList<>(origin.subList(0, middle));
      case RIGHT:
        return new ArrayList<>(origin.subList(middle, size));
      default:
        return null;
    }
  }

  public static List<Integer> cast2IntegerList(final String s, char separator) {
    String s1 = cast2String(s, null);
    if (StringUtils.isBlank(s1)) {
      return null;
    }
    String[] arr = StringUtils.split(s1, separator);
    if (ArrayUtils.isEmpty(arr)) {
      return null;
    }
    List<Integer> list = Lists.newArrayListWithExpectedSize(arr.length);
    for (String s2 : arr) {
      list.add(Integer.valueOf(s2));
    }
    return list;
  }

  public static Set<Integer> cast2IntegerSet(final String s, char separator) {
    String s1 = cast2String(s, null);
    if (StringUtils.isBlank(s1)) {
      return null;
    }
    String[] arr = StringUtils.split(s1, separator);
    if (ArrayUtils.isEmpty(arr)) {
      return null;
    }
    Set<Integer> set = Sets.newHashSetWithExpectedSize(arr.length);
    for (String s2 : arr) {
      set.add(Integer.valueOf(s2));
    }
    return set;
  }

  public static String cast2String(Object o) {
    return cast2String(o, null);
  }

  /**
   * 不论传入的是什么, 都会对其stringValue进行trim2null操作. 因此加入传入的是空串, \s等, 也会trim到null
   *
   * @param o
   * @param defaultValue
   * @return
   */
  public static String cast2String(Object o, String defaultValue) {
    if (o == null) {
      return defaultValue;
    }
    String s = StringUtils.trimToNull(String.valueOf(o));
    return s == null ? defaultValue : s;
  }

  public static int cast2Int(Object o) {
    return cast2Int(o, 0);
  }

  public static int cast2Int(Object o, int defaultValue) {
    if (o == null) {
      return defaultValue;
    }
    if (o instanceof Integer) {
      return ((Integer) o).intValue();
    }
    String s = StringUtils.trimToNull(String.valueOf(o));
    return StringUtils.isBlank(s) ? defaultValue : Integer.parseInt(s);
  }

  public static double cast2Double(Object o) {
    return cast2Double(o, 0d);
  }

  public static double cast2Double(Object o, double defaultValue) {
    if (o == null) {
      return defaultValue;
    }
    if (o instanceof Double) {
      return ((Double) o).doubleValue();
    }
    String s = StringUtils.trimToNull(String.valueOf(o));
    return StringUtils.isBlank(s) ? defaultValue : Double.parseDouble(s);
  }

  public static boolean cast2Boolelan(Object o) {
    return cast2Boolelan(o, false);
  }

  public static boolean cast2Boolelan(Object o, boolean defaultValue) {
    if (o == null) {
      return defaultValue;
    }
    if (o instanceof Boolean) {
      return ((Boolean) o).booleanValue();
    }
    String s = StringUtils.trimToNull(String.valueOf(o));
    return StringUtils.isBlank(s) ? defaultValue : Boolean.parseBoolean(s);
  }

  /**
   * 至少符合1个条件
   *
   * @param conditions
   * @return
   * @throws Exception
   */
  public static boolean atLeast(boolean... conditions) throws Exception {
    for (boolean b : conditions) {
      if (b) {
        return true;
      }
    }
    return false;
  }

  /**
   * 一个都不符合
   *
   * @param conditions
   * @return
   * @throws Exception
   */
  public static boolean noneOf(boolean... conditions) throws Exception {
    for (boolean b : conditions) {
      if (b) {
        return false;
      }
    }
    return true;
  }

  /**
   * 必须符合所有条件
   *
   * @param conditions
   * @return
   * @throws Exception
   */
  public static boolean allOf(boolean... conditions) throws Exception {
    for (boolean b : conditions) {
      if (!b) {
        return false;
      }
    }
    return true;
  }

  public static String toBinaryString(byte b) {
    char[] buf = new char[LENGTH_OF_BYTE];
    for (int i = 0; i < LENGTH_OF_BYTE; i++) {
      int s = LENGTH_OF_BYTE - 1 - i, mask = 1 << s;
      buf[i] = digits[(b & mask) >>> s];
    }
    return new String(buf);
  }

  public static String getRequestId() {
    StringBuilder sb = new StringBuilder();
    sb.append(System.currentTimeMillis());
    sb.append(RandomStringUtils.randomAlphanumeric(20));
    return DigestUtils.sha1Hex(sb.toString());
  }

  public static <T extends Enum<T>> T cast2Enum(Object o, Class<T> t) {
    if (o == null) {
      return null;
    }
    String enumString = StringUtils.trimToNull(String.valueOf(o));
    if (StringUtils.isBlank(enumString)) {
      return null;
    }
    return EnumUtils.getEnum(t, enumString);
  }

  public static <T> T choseOne(List<? extends T> src) {
    if (CollectionUtils.isEmpty(src)) {
      return null;
    }
    int i = RANDOM.nextInt(src.size());
    return src.get(i);
  }

  public static <T> int chose(List<? extends T> src, List<T> dist, int maxAmount) {
    return chose(src, dist, maxAmount, 10000);
  }

  public static <T> int chose(List<? extends T> src, List<T> dist, int maxAmount,
                              Collection<T>... blackList) {
    return chose(src, dist, maxAmount, 10000, blackList);
  }

  public static <T> int chose(List<? extends T> src, List<T> dist, int choseAmount,
                              int maxRetryWhenConflict, Collection<? extends T>... blackLists) {
    if (choseAmount <= 0 || CollectionUtils.isEmpty(src)) {
      return 0;
    }

    List<? extends T> list;
    if (ArrayUtils.isEmpty(blackLists)) {
      list = src;
    } else {
      Set<T> allBlackList = Sets.newHashSet();
      for (Collection<? extends T> blackList : blackLists) {
        if (CollectionUtils.isEmpty(blackList)) {
          continue;
        }
        allBlackList.addAll(blackList);
      }
      if (CollectionUtils.isEmpty(allBlackList)) {
        list = src;
      } else {
        list = Lists.newArrayList(subtract(src, allBlackList));
        if (CollectionUtils.isEmpty(list)) {
          return 0;
        }
      }
    }

    int srcSize = list.size(), realChooseAmount = 0;
    if (srcSize <= choseAmount) {
      List<T> sub = Lists.newArrayList(list);
      Collections.shuffle(sub);
      dist.addAll(sub);
      realChooseAmount = srcSize;
    } else {
      BitSet bs = new BitSet(srcSize);
      for (int i = 0; i < choseAmount; i++) {
        int chosenIndex = RANDOM.nextInt(srcSize);
        for (int j = 0; j < maxRetryWhenConflict; j++) {
          if (!bs.get(chosenIndex)) {
            break;
          }
          chosenIndex = RANDOM.nextInt(srcSize);
        }
        if (!bs.get(chosenIndex)) {
          dist.add(list.get(chosenIndex));
          bs.set(chosenIndex);
          ++realChooseAmount;
        }
      }
    }
    return realChooseAmount;
  }

  public static long timeCost(long t1) {
    return System.currentTimeMillis() - t1;
  }

}
